<!DOCTYPE html>






<html class="theme-next muse use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">







  <script src="/lib/pace/pace.min.js?v=1.0.2"></script>
  <link href="/lib/pace/pace-theme-minimal.min.css?v=1.0.2" rel="stylesheet">







<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">


















  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">



















































<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.3" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.3">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.3">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.3">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.3" color="#222">





  <meta name="keywords" content="Android, Java">










<meta name="description" content="哈哈~">
<meta name="keywords" content="zachaxy">
<meta property="og:type" content="website">
<meta property="og:title" content="zachaxy">
<meta property="og:url" content="https://zachaxy.github.io/page/8/index.html">
<meta property="og:site_name" content="zachaxy">
<meta property="og:description" content="哈哈~">
<meta property="og:locale" content="zh-Hans">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="zachaxy">
<meta name="twitter:description" content="哈哈~">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Muse',
    version: '5.1.3',
    sidebar: {"position":"right","display":"hide","offset":12,"b2t":false,"scrollpercent":true,"onmobile":true},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: 'undefined',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://zachaxy.github.io/page/8/">





  <title>zachaxy</title>









</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">






  <div class="container sidebar-position-right
  page-home">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">


    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">zachaxy</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>

        <h1 class="site-subtitle" itemprop="description">纵情向前！</h1>

  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">



    <ul id="menu" class="menu">


        <li class="menu-item menu-item-home">
          <a href="/" rel="section">

              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>

            首页
          </a>
        </li>


        <li class="menu-item menu-item-tags">
          <a href="/tags" rel="section">

              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>

            标签
          </a>
        </li>


        <li class="menu-item menu-item-archives">
          <a href="/archives" rel="section">

              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>

            归档
          </a>
        </li>


        <li class="menu-item menu-item-about">
          <a href="/about" rel="section">

              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>

            关于
          </a>
        </li>



        <li class="menu-item menu-item-search">

            <a href="javascript:;" class="popup-trigger">


              <i class="menu-item-icon fa fa-search fa-fw"></i> <br>

            搜索
          </a>
        </li>

    </ul>



    <div class="site-search">

  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off" placeholder="搜索..." spellcheck="false" type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>

</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">

  <section id="posts" class="posts-expand">









  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">



  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zachaxy.github.io/2017/05/24/海量数据场景下的解决思路/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zachaxy">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zachaxy">
    </span>


      <header class="post-header">



          <h2 class="post-title" itemprop="name headline">

                <a class="post-title-link" href="/2017/05/24/海量数据场景下的解决思路/" itemprop="url">海量数据场景下的解决思路</a></h2>


        <div class="post-meta">
          <span class="post-time">

              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>

                <span class="post-meta-item-text">发表于</span>

              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-05-24T14:59:38+08:00">
                2017-05-24
              </time>





          </span>













            <div class="post-wordcount">


                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>

                  <span class="post-meta-item-text">字数统计&#58;</span>

                <span title="字数统计">
                  2,051
                </span>





            </div>




        </div>
      </header>





    <div class="post-body" itemprop="articleBody">







            <p><strong>1.海量日志数据，提取出现次数最多的一条(IP)</strong><br>注意,这里说的是 IP,那么<strong>IP 是有大小范围的</strong>,是 32 位的<br>首先把海量数据放在一个文件中,接下来对已每个 IP 都进行<strong>mod(1000)</strong>,那么这样的话就会产生 1000 个小文件,将海量数据进行了分散,针对这 1000 个文件中的每个小文件,使用 hashMap 进行统计,其中将 IP 作为 key,该条 IP 出现的次数作为 value。这样统计出每个小文件中出现最多的 IP,这样就取到了 1000 个 IP,然后从这 1000 个 IP 中找到出现次数最大的一个。</p>
<p><strong>2.海量字符串(基于搜索热度),提取出现搜索词最多的十条记录</strong><br>注意,这些海量搜索词中很多是重复的,所以字符串的种类其实并不多,可能只有原来的一半的样子,这时我们使用一个 hashMap 来保存每个字符串的引用和出现次数,内存是可以放的下的,所求是出现次数最多的,然后使用堆排序中的大根堆,找到最大的 10 个。</p>
<p><strong>3.一个 1G 大小的一个文件，里面每一行是一个词，词的大小不超过 16 字节，内存限制大小是 1M。返回频数最高的 100 个词</strong></p>
<p>这个题目就没有前一个题目那么幸运了,也许没有很多重复的单词，我们无法将全部内容都读取到内存中,所以需要<strong>分成小文件</strong>;</p>
<p>1G 大小，每个单词 16 字节，那么一共有 <code>1G/16byte = 2^26</code>个单词</p>
<p>顺序读取该大文件，对于每个词 x，取 hash(x)%5000，然后按照该值存到 5000 个小文件（记为 x0,x1,…x4999）中(平均每个文件大约 20w 个单词)。这样每个文件大概是 200k 左右(如果单词比较随机的话)。<br>对于每个文件,使用 HashMap 统计每个文件中出现的词以及相应的频率,然后取出每个文件中出现频率最高的 100 个词(用最大堆排好序),这样就得到了 5000*100 个词,放到一个数组中,进行归并排序即可;</p>
<p><strong>4.有 10 个文件，每个文件 1G，每个文件的每一行存放的都是用户的 query，每个文件的 query 都可能重复。要求按照 query 的频度排序。</strong><br>顺序读取 10 个文件，按照 hash(query)%10 的结果将 query 写入到另外 10 个文件（记为）中。这样新生成的文件每个的大小大约也 1G（假设 hash 函数是随机的）。</p>
<p>分好这 10 个文件之后,就需要进行着 10 个文件,进行快速/堆/归并排序按照出现次数进行排序,然后这 10 个文件时排好序的,在对这 10 个文件进行归并排序;<br>​<br>找一台内存在 2G 左右的机器，依次对用 hashMap(query,queryCount)来统计每个 query 出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的 query 和对应的 queryCout 输出到文件中。这样得到了 10 个排好序的文件。</p>
<p><strong>5.给定 a、b 两个文件，各存放 50 亿个 url，每个 url 各占 64 字节，内存限制是 4G，让你找出 a、b 文件共同的 url？</strong><br>遍历文件 a，对每个 url 求取 hash(url)%1000，然后根据所取得的值将 url 分别存储到 1000 个小文件（记为 a0,a1,…,a999）中。这样每个小文件的大约为 300M。</p>
<p>遍历文件 b，对每个 url 求取 hash(url)%1000，然后根据所取得的值将 url 分别存储到 1000 个小文件（记为 b0,b1,…,b999）中。这样每个小文件的大约为 300M。</p>
<p>那么接下来针对每一个 ax VS bx 文件（这时可以保证每个如果是相同的 url，都在相同的 ax 或 bx 中）,可以把其中一个小文件的 url 存储到 hashSet 中。然后遍历另一个小文件的每个 url，看其是否在刚才构建的 hashSet 中，如果是，那么就是共同的 url，存到文件里面就可以了。</p>
<p><strong>6.在 2.5 亿个整数中找出不重复的整数的个数</strong><br>既然是整数,那么整数最多也就是 2^32 个,定义一个 2bit 的标志,00 没有出现过,01,出现一次;10,出现多次;11,无意义;那么整个 2^32<em>2bit 是完全可以在内存中放得下的<br>接下来遍历一次 2.5 亿个数,然后映射到 2bit 中,然后再遍历一次 2^32</em>2bit 的内存空间,只找 01 标志位的;</p>
<p><strong>7.给 40 亿个不重复的 unsigned int 的整数，没排过序的，然后再给一个数，如何快速判断这个数是否在那 40 亿个数当中？</strong><br>同 6 一样,将这 40 亿个数映射到一个 1bit 的内存中,如果是 0 则没出现过,如果是 1 则出现过;</p>
<p>方案 2:将这 40 亿个数按照每一位的 0 或 1 来分开：</p>
<p>2^32 为 40 亿多，所以给定一个数可能在，也可能不在其中；这里我们把 40 亿个数中的每一个用 32 位的二进制来表示假设这 40 亿个数开始放在一个文件中。</p>
<p>然后将这 40 亿个数分成两类:<br>1.最高位为 0<br>2.最高位为 1<br>并将这两类分别写入到两个文件中，其中一个文件中数的个数&lt;=20 亿，而另一个&gt;=20 亿（这相当于折半了）；</p>
<p>与要查找的数的最高位比较并接着进入相应的文件再查找</p>
<p>再然后把这个文件为又分成两类:<br>1.次最高位为 0<br>2.次最高位为 1<br>​<br>并将这两类分别写入到两个文件中，其中一个文件中数的个数&lt;=10 亿，而另一个&gt;=10 亿（这相当于折半了）；<br>与要查找的数的次最高位比较并接着进入相应的文件再查找。<br>…….<br>以此类推，就可以找到了,而且时间复杂度为 O(logn)。</p>
<p><strong>8.海量数据中找出重复次数最多的一个</strong><br>先做 hash，然后<strong>求模映射为小文件</strong>，求出每个小文件中重复次数最多的一个，并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求（具体参考前面的题）。</p>
<p> <strong>9.海量数据中找出重复次数最多的 N 个</strong><br>首先还是用 hash 进行分类,假设分成 1k 个组,对这 1k 个组中,分别进行堆排序,找出前 N 个,现在就有 1K*N 个数据,那么接下来就是对这 1K 个组进行归并或者堆排序。</p>
<p><strong>10.一个文本文件，大约有一万行，每行一个词，要求统计出其中最频繁出现的前 10 个词，请给出思想，给出时间复杂度分析。</strong></p>
<p>方案 1：这题是考虑时间效率。用<strong>trie 树</strong>统计每个词出现的次数，时间复杂度是<code>O(n*le)</code>（le 表示单词的平均长度）。然后是找出出现最频繁的前 10 个词，可以用堆来实现，前面的题中已经讲到了，时间复杂度是<code>O(n*lg10)</code>。所以总的时间复杂度，是<code>O(n*le)</code>与 O<code>(n*lg10)</code>中较大的哪一个。</p>
<p>方案 2：先将这 1w 个单词进行 hash（eg：mod 10），分成 1000 个组，每组创建最大堆，维持 10 个元素，然后在对着 1000 个组中的 top10 进行归并，找到前 10 个即停止。</p>
<p><strong>11.100w 个数中找出最大的 100 个数。</strong><br>在前面的题中，我们已经提到了，用一个含 100 个元素的最大堆完成。复杂度为<code>O(100w*lg100)</code>。或者先分组（组内用堆排序），然后归并，找到前 100 即停止。</p>
<p>参考：<br><a href="http://blog.csdn.net/v_july_v/article/details/6279498" target="_blank" rel="noopener">十道海量数据处理面试题与十个方法大总结</a><br><a href="http://taop.marchtea.com/06.02.html" target="_blank" rel="noopener">分而治之</a></p>




    </div>










    <footer class="post-footer">








        <div class="post-eof"></div>

    </footer>
  </div>



  </article>











  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">



  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zachaxy.github.io/2017/05/23/Volley源码解析-缓存/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zachaxy">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zachaxy">
    </span>


      <header class="post-header">



          <h2 class="post-title" itemprop="name headline">

                <a class="post-title-link" href="/2017/05/23/Volley源码解析-缓存/" itemprop="url">Volley源码解析-缓存</a></h2>


        <div class="post-meta">
          <span class="post-time">

              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>

                <span class="post-meta-item-text">发表于</span>

              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-05-23T14:48:12+08:00">
                2017-05-23
              </time>





          </span>













            <div class="post-wordcount">


                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>

                  <span class="post-meta-item-text">字数统计&#58;</span>

                <span title="字数统计">
                  2,343
                </span>





            </div>




        </div>
      </header>





    <div class="post-body" itemprop="articleBody">







            <h1 id="Cache-接口"><a href="#Cache-接口" class="headerlink" title="Cache 接口"></a>Cache 接口</h1><p>我们再来回顾一下一个 Request 被添加到 RequestQueue 后，首先是被添加到 mCacheQueue 中，而不是添加到 mNetworkQueue，因为这个请求可能之前执行过，先看一下缓存中有没有，如果没有或者过期，那么再将该请求从 mCacheQueue 中移除，并添加到 mNetworkQueue。</p>
<p>那么 Cache 中保存了哪些信息呢？</p>
<p>代码过多这里就不贴出来了，首先作为缓存，肯定要指定一个缓存文件夹以及其保存的位置，接着每个缓存最好是以键值对的形式存在，使用 HashMap 结构来保存，这样才能快速的通过 key 查找 value，key 对应的是一个 request 的 url，value 对应的是 request 得到的 response；</p>
<p>接下来，response 中需要保存哪些数据呢？首先肯定要保存响应体中的数据，使用字节数组保存。同时还应该保存这个请求的 Etag，有效时长等和 HTTP 缓存相关的字段，以及其它的响应头信息，这些信息被封装在一个 Entry 的类中，该类是 Cache 接口中的静态内部类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Entry</span> </span>&#123;</span><br><span class="line">    <span class="comment">/** The data returned from cache. */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">byte</span>[] data;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** ETag for cache coherency. */</span></span><br><span class="line">    <span class="keyword">public</span> String etag;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** Date of this response as reported by the server. */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">long</span> serverDate;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** TTL for this record. */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">long</span> ttl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** Soft TTL for this record. */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">long</span> softTtl;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** Immutable response headers as received from server; must be non-null. */</span></span><br><span class="line">    <span class="keyword">public</span> Map&lt;String, String&gt; responseHeaders = Collections.emptyMap();</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** True if the entry is expired. */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">isExpired</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.ttl &lt; System.currentTimeMillis();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/** True if a refresh is needed from the original data source. */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">refreshNeeded</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>.softTtl &lt; System.currentTimeMillis();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="DiskBasedCache"><a href="#DiskBasedCache" class="headerlink" title="DiskBasedCache"></a>DiskBasedCache</h2><p>前面讲了 Cache 的整体实现思路，接下来设计到具体如何读写缓存，这就用到了 Cache 的实现类 DiskBasedCache</p>
<p>这里只列出方法名和内部实现流程，就不一行一行的写了，因为太多了！！！</p>
<ul>
<li><p><code>private final Map&lt;String, CacheHeader&gt; mEntries = new LinkedHashMap&lt;String, CacheHeader&gt;(16, .75f, true)</code>是整个缓存的 map</p>
</li>
<li><p><code>CacheHeader</code>是map 中保存的 value，DiskBasedCache 中的静态内部类，是缓存文件中内容的一个概述，不包含 Cache 中 Entry 类中的 data 数据，因为这是要读取到内存中的，如果带上响应体数据，那么会占用很大一部分内存。</p>
<p> Cache.Entry 和 DiskBasedCache.CacheHeader 之间的关系：<strong>Entry = CacheHeader + byte[] data</strong></p>
<ul>
<li><p><code>writeHeader(OutputStream os)</code>:直接看其是如何将 CacheHeader 写到到文件中的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">writeHeader</span><span class="params">(OutputStream os)</span> </span>&#123;</span><br><span class="line">	<span class="keyword">try</span> &#123;</span><br><span class="line">		writeInt(os, CACHE_MAGIC);</span><br><span class="line">		writeString(os, key);</span><br><span class="line">		writeString(os, etag == <span class="keyword">null</span> ? <span class="string">""</span> : etag);</span><br><span class="line">		writeLong(os, serverDate);</span><br><span class="line">		writeLong(os, ttl);</span><br><span class="line">		writeLong(os, softTtl);</span><br><span class="line">		writeStringStringMap(responseHeaders, os);</span><br><span class="line">		os.flush();</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">	&#125; <span class="keyword">catch</span> (IOException e) &#123;</span><br><span class="line">		VolleyLog.d(<span class="string">"%s"</span>, e.toString());</span><br><span class="line">		<span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</li>
<li><p><code>readHeader(InputStream is)</code>和上面的 write 方法是相反的，怎么写的怎么读</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> CacheHeader <span class="title">readHeader</span><span class="params">(InputStream is)</span> <span class="keyword">throws</span> IOException </span>&#123;</span><br><span class="line">	CacheHeader entry = <span class="keyword">new</span> CacheHeader();</span><br><span class="line">	<span class="keyword">int</span> magic = readInt(is);</span><br><span class="line">	<span class="keyword">if</span> (magic != CACHE_MAGIC) &#123;</span><br><span class="line">		<span class="comment">// don't bother deleting, it'll get pruned eventually</span></span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> IOException();</span><br><span class="line">	&#125;</span><br><span class="line">	entry.key = readString(is);</span><br><span class="line">	entry.etag = readString(is);</span><br><span class="line">	<span class="keyword">if</span> (entry.etag.equals(<span class="string">""</span>)) &#123;</span><br><span class="line">		entry.etag = <span class="keyword">null</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	entry.serverDate = readLong(is);</span><br><span class="line">	entry.ttl = readLong(is);</span><br><span class="line">	entry.softTtl = readLong(is);</span><br><span class="line">	entry.responseHeaders = readStringStringMap(is);</span><br><span class="line">	<span class="keyword">return</span> entry;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里有一个很有意思的<strong>写缓存文件的技巧</strong>，因为这里涉及到了写 int，long，String，那么如何去写呢？ 方法是：如果你写的是 int，那么就用 4 字节来表示，如果是 long，那么就用 8 字节来表示，这样在读的时候，只要是读 int，那么直接向后读 4 个字节，然后拼装成 int 即可；至于字符串，统一将其转换成 UTF8 格式的字节数据，并得到数组长度，首先写入 long 类型的长度，然后再将字节数组写入，在读取的时候先读取 8 字节的 long 值，得到后续字节数组的长度，然后在读取后面相应长度的字节数组，转换为字符串即可。</p>
</li>
</ul>
</li>
<li><p><code>public synchronized void initialize()</code>初始化上面的 map，遍历缓存文件夹，读取每个缓存文件，然后不断的调用 CacheHeader 的 readHeader 方法读取 CacheHeader，并添加到 map 中</p>
</li>
<li><p><code>public synchronized void put(String key, Entry entry)</code>首先调用 CacheHeader 的 writeHeader 方法将数据写入缓存文件，在将 data 数据额外写入缓存文件；同时将该 entry 对应的 entryHeader 添加到 map 中；</p>
</li>
<li><p><code>public synchronized Entry get(String key)</code>先从 map 中通过 key 得到 CacheHeader，在根据 key 得到缓存文件名（将 key 的字符串均分为前后两部分，前半部分的 hashCode 和后半部分的 hashCode 拼接起来），然后读取缓存文件中的 data 数据，和 CacheHeader 组合起来得到 Entry。</p>
</li>
<li><p><code>public synchronized void remove(String key)</code>先从 map 中删除掉 key 对应的数据，然后删除缓存文件中对应的文件</p>
</li>
<li><p><code>public synchronized void clear()</code>：清空缓存，可以在应用的设置中提供这一项。有一个对应的 ClearCacheRequest，专门负责清空缓存。</p>
</li>
</ul>
<h1 id="CacheDispatcher-线程"><a href="#CacheDispatcher-线程" class="headerlink" title="CacheDispatcher 线程"></a>CacheDispatcher 线程</h1><h2 id="构造方法"><a href="#构造方法" class="headerlink" title="构造方法"></a>构造方法</h2><p>CacheDispatcher 线程是会不断的执行读缓存的操作，如果能读到缓存，那么将数据用 ResponseDelivery 发送到 UI 线程，否则将 request 提交给 NetworkDispatcher 来处理。</p>
<p>首先看其构造方法，传入了在 RequestQueue 中创建的 mCacheQueue, mNetworkQueue, mCache, mDelivery 四个变量。注意这个 mCache 是在 RequestQueue 的构造方法中传入的第一个参数 Cache。也就是说 CacheDispatcher 内部只有这几个成员变量。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">CacheDispatcher</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">        BlockingQueue&lt;Request&gt; cacheQueue, BlockingQueue&lt;Request&gt; networkQueue,</span></span></span><br><span class="line"><span class="function"><span class="params">        Cache cache, ResponseDelivery delivery)</span> </span>&#123;</span><br><span class="line">    mCacheQueue = cacheQueue;</span><br><span class="line">    mNetworkQueue = networkQueue;</span><br><span class="line">    mCache = cache;</span><br><span class="line">    mDelivery = delivery;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="run-方法"><a href="#run-方法" class="headerlink" title="run 方法"></a>run 方法</h2><p>接下来看一下其 run 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (DEBUG) VolleyLog.v(<span class="string">"start new dispatcher"</span>);</span><br><span class="line">    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Make a blocking call to initialize the cache.</span></span><br><span class="line">    mCache.initialize();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">// Get a request from the cache triage queue, blocking until</span></span><br><span class="line">            <span class="comment">// at least one is available.</span></span><br><span class="line">            <span class="keyword">final</span> Request request = mCacheQueue.take();</span><br><span class="line">            request.addMarker(<span class="string">"cache-queue-take"</span>);</span><br><span class="line"></span><br><span class="line">            <span class="comment">// If the request has been canceled, don't bother dispatching it.</span></span><br><span class="line">            <span class="keyword">if</span> (request.isCanceled()) &#123;</span><br><span class="line">                request.finish(<span class="string">"cache-discard-canceled"</span>);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// Attempt to retrieve this item from cache.</span></span><br><span class="line">            Cache.Entry entry = mCache.get(request.getCacheKey());</span><br><span class="line">            <span class="keyword">if</span> (entry == <span class="keyword">null</span>) &#123;</span><br><span class="line">                request.addMarker(<span class="string">"cache-miss"</span>);</span><br><span class="line">                <span class="comment">// Cache miss; send off to the network dispatcher.</span></span><br><span class="line">                mNetworkQueue.put(request);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// If it is completely expired, just send it to the network.</span></span><br><span class="line">            <span class="keyword">if</span> (entry.isExpired()) &#123;</span><br><span class="line">                request.addMarker(<span class="string">"cache-hit-expired"</span>);</span><br><span class="line">                request.setCacheEntry(entry);</span><br><span class="line">                mNetworkQueue.put(request);</span><br><span class="line">                <span class="keyword">continue</span>;</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">            <span class="comment">// We have a cache hit; parse its data for delivery back to the request.</span></span><br><span class="line">            request.addMarker(<span class="string">"cache-hit"</span>);</span><br><span class="line">            Response&lt;?&gt; response = request.parseNetworkResponse(</span><br><span class="line">                    <span class="keyword">new</span> NetworkResponse(entry.data, entry.responseHeaders));</span><br><span class="line">            request.addMarker(<span class="string">"cache-hit-parsed"</span>);</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> (!entry.refreshNeeded()) &#123;</span><br><span class="line">                <span class="comment">// Completely unexpired cache hit. Just deliver the response.</span></span><br><span class="line">                mDelivery.postResponse(request, response);</span><br><span class="line">            &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                <span class="comment">// Soft-expired cache hit. We can deliver the cached response,</span></span><br><span class="line">                <span class="comment">// but we need to also send the request to the network for</span></span><br><span class="line">                <span class="comment">// refreshing.</span></span><br><span class="line">                request.addMarker(<span class="string">"cache-hit-refresh-needed"</span>);</span><br><span class="line">                request.setCacheEntry(entry);</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Mark the response as intermediate.</span></span><br><span class="line">                response.intermediate = <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// Post the intermediate response back to the user and have</span></span><br><span class="line">                <span class="comment">// the delivery then forward the request along to the network.</span></span><br><span class="line">                mDelivery.postResponse(request, response, <span class="keyword">new</span> Runnable() &#123;</span><br><span class="line">                    <span class="meta">@Override</span></span><br><span class="line">                    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>&#123;</span><br><span class="line">                        <span class="keyword">try</span> &#123;</span><br><span class="line">                            mNetworkQueue.put(request);</span><br><span class="line">                        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">                            <span class="comment">// Not much we can do about this.</span></span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125;);</span><br><span class="line">            &#125;</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            <span class="comment">// We may have been interrupted because it was time to quit.</span></span><br><span class="line">            <span class="keyword">if</span> (mQuit) &#123;</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            &#125;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第 4 行，设置当前线程的优先级为：Process.THREAD_PRIORITY_BACKGROUND，标准后台线程，相对来说优先级还是比较低的。</p>
<p>第 7 行，因为这里是 Cache，需要先进行一些初始化，这里调用了 Cache 的 initialize 方法，内部逻辑在上面已经说过了。</p>
<p>第 9 行，死循环，表明该线程不一直不断的从 mCacheQueue 中取 request</p>
<p>第 13 行，从 BlockingQueue 中取出一个 request，如果没有的话，就会被阻塞，也不会占用系统资源，赞。</p>
<p>第 17 行，如果该请求在执行之前，又被其他线程调用了 cancle 方法，那么就取消该 request 的执行。</p>
<p>第 23 行，从缓存中查看是否有该 request 的缓存，如果不存在，表明是一个新的请求，那么直接将该请求添加到 mNetworkQueue 中，下面的逻辑不用执行，重新从 BlockingQueue 中取 request</p>
<p>第 32 行，能走到这一行，表明缓存不为空，判断该缓存是否已经过期，这里判断的标准是：<code>this.ttl &lt; System.currentTimeMillis();</code>现在 HTTP1.1 的版本里面保存的都是存活时间，而不是绝对时间，但是考虑到我们的客户端可能会退出，所以无法用时间段来统计，这里还是转换成相对于客户端的绝对时间，再和当前时间进行比较。如果缓存过期了，那么需要将该 request 添加到 mNetworkQueue 中重新进行网络请求，但是在此之前，还要把缓存中的一些信息(“If-None-Match”&lt;=&gt;”etag”，”If-Modified-Since”&lt;=&gt;”serverDate”)附加到 request 中</p>
<p>第 41 行，能走到这一步，说明我们的缓存命中并且是可用的，此时需要把缓存中的数据读取出来，包装成 Response</p>
<p><code>Response&lt;?&gt; response = request.parseNetworkResponse(new NetworkResponse(entry.data, entry.responseHeaders));</code></p>
<p>第 45 行，虽然现在缓存已被转换为 Response，但是还要进行一步新鲜度验证，但是这一步新鲜度验证时多余的，因为 Entry 中的 softTtl 和 ttl 的值是相同的，而前面已经验证了是否过期，所以这一步是多余的，直接使用 ResponseDelivery 分发响应，关于响应的分发，请参阅<a href>Volley 源码解析-线程切换</a></p>
<p>如果不是新鲜的，那么首先将该 response 分发到 UI 线程显示，同时立即执行网络请求，获取最新的响应。</p>




    </div>










    <footer class="post-footer">








        <div class="post-eof"></div>

    </footer>
  </div>



  </article>











  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">



  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zachaxy.github.io/2017/05/22/Volley源码解析-请求队列/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zachaxy">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zachaxy">
    </span>


      <header class="post-header">



          <h2 class="post-title" itemprop="name headline">

                <a class="post-title-link" href="/2017/05/22/Volley源码解析-请求队列/" itemprop="url">Volley源码解析-请求队列</a></h2>


        <div class="post-meta">
          <span class="post-time">

              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>

                <span class="post-meta-item-text">发表于</span>

              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-05-22T19:23:17+08:00">
                2017-05-22
              </time>





          </span>













            <div class="post-wordcount">


                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>

                  <span class="post-meta-item-text">字数统计&#58;</span>

                <span title="字数统计">
                  3,640
                </span>





            </div>




        </div>
      </header>





    <div class="post-body" itemprop="articleBody">







            <h1 id="整体理解"><a href="#整体理解" class="headerlink" title="整体理解"></a>整体理解</h1><p><a href="https://zachaxy.github.io/2017/05/20/Volley%E6%A1%86%E6%9E%B6%E8%AF%A6%E8%A7%A3-%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/#自定义请求">上一节</a>，我们介绍了 Volley 的基本使用，再次回顾一下其整体流程：</p>
<ol>
<li>创建 RequestQueue 队列</li>
<li>创建 XXXRequest</li>
<li>将 2 中创建的 request 添加到 1 中创建的 RequestQueue 队列中</li>
</ol>
<p>整个流程就这么简单，添加到 RequestQueue 队列之后，队列中的请求就会自动被执行，我们只需要等待请求中的回调方法被调用即可。那么其内部是如何实现的呢？猜想：后台一定有一个子线程，不断的轮询 RequestQueue，如果其中有请求，那么就会执行网络请求，因为这是在子线程中执行，所以不会阻塞 UI 线程，等服务器有响应后，执行请求中的回调。所以如果让我们来设计一套网络请求框架的话，最基本的一定是这个思路。</p>
<p>那还等什么，马上打开源码验证一下我们的猜想对不对吧！等等，似乎还少了什么，有可能会出现这种情况，如果我们重复的执行某一个相同的请求（eg：不断的打开相同的图片），图片资源一般在短时间内很少改变的，如果我们重复执行网络请求，似乎不是很合理，所以对于相同的网络请求（相同的 url），我们应该将结果缓存起来，这样也符合 http 协议中缓存的思想，这样就避免消耗过多的流量。</p>
<p>那么接下来，我们就按照 Volley 使用的三步流程开始，进入源码，剖析 Volley 的内部流程，首先介绍的是创建请求队列：</p>
<h1 id="创建-RequestQueue-队列"><a href="#创建-RequestQueue-队列" class="headerlink" title="创建 RequestQueue 队列"></a>创建 RequestQueue 队列</h1><p>在介绍 RequestQueue 队列之前，这里先介绍几个关键的类，以便于我们更好的理解 Volley。</p>
<ul>
<li><strong>HttpStack：</strong>处理 Http 请求，返回请求结果。目前 Volley 中有基于 HttpURLConnection 的<code>HurlStack</code>和 基于 Apache HttpClient 的<code>HttpClientStack</code>。关于 HttpStack 的详细理解，请参考<a href>Volley 源码解析-网络</a></li>
<li><strong>Volley：</strong>Volley 对外暴露的 API，通过 newRequestQueue 方法新建并启动一个请求队列<code>RequestQueue</code>。</li>
<li><strong>Request：</strong>表示一个请求的抽象类。<code>StringRequest</code>、<code>JsonRequest</code>、<code>ImageRequest</code> 都是它的子类，表示某种类型的请求。</li>
<li><strong>ResponseDelivery：</strong>返回结果分发接口，目前只有基于<code>ExecutorDelivery</code>的在入参 handler 对应线程内进行分发。</li>
<li><strong>Network：</strong>调用<code>HttpStack</code>处理请求，并将结果转换为可被<code>ResponseDelivery</code>处理的<code>NetworkResponse</code>。</li>
<li><strong>Cache：</strong>缓存请求结果，Volley 默认使用的是基于 sdcard 的<code>DiskBasedCache</code>。<code>NetworkDispatcher</code>得到请求结果后判断是否需要存储在 Cache，<code>CacheDispatcher</code>会从 Cache 中取缓存结果。</li>
</ul>
<p>创建请求队列是我们在应用程序中是这样调用的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RequestQueue mQueue = Volley.newRequestQueue(context);</span><br></pre></td></tr></table></figure>

<p>其内部调用的 Volley 类中的静态方法，源码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> RequestQueue <span class="title">newRequestQueue</span><span class="params">(Context context)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> newRequestQueue(context, <span class="keyword">null</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们默认值传入了第一个参数 Context，第二个参数 HttpStack 默认为 null，接下来调用下面的方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> RequestQueue <span class="title">newRequestQueue</span><span class="params">(Context context, HttpStack stack)</span> </span>&#123;</span><br><span class="line">    File cacheDir = <span class="keyword">new</span> File(context.getCacheDir(), DEFAULT_CACHE_DIR);</span><br><span class="line"></span><br><span class="line">    String userAgent = <span class="string">"volley/0"</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        String packageName = context.getPackageName();</span><br><span class="line">        PackageInfo info = context.getPackageManager().getPackageInfo(packageName, <span class="number">0</span>);</span><br><span class="line">        userAgent = packageName + <span class="string">"/"</span> + info.versionCode;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (NameNotFoundException e) &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (stack == <span class="keyword">null</span>) &#123;</span><br><span class="line">        <span class="keyword">if</span> (Build.VERSION.SDK_INT &gt;= <span class="number">9</span>) &#123;</span><br><span class="line">            stack = <span class="keyword">new</span> HurlStack();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">// Prior to Gingerbread, HttpUrlConnection was unreliable.</span></span><br><span class="line">            <span class="comment">// See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html</span></span><br><span class="line">            stack = <span class="keyword">new</span> HttpClientStack(AndroidHttpClient.newInstance(userAgent));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    Network network = <span class="keyword">new</span> BasicNetwork(stack);</span><br><span class="line">    Cache cache = <span class="keyword">new</span> DiskBasedCache(cacheDir);</span><br><span class="line"></span><br><span class="line">    RequestQueue queue = <span class="keyword">new</span> RequestQueue(cache, network);</span><br><span class="line">    queue.start();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> queue;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第 2 行，创建一个文件夹，保存网络的请求缓存，默认路径是在：“/data/data/com.xxx.xxx（当前包）/cache/volley/”</p>
<p>第 4 行，创建了 User Agent，其默认值是：”volley/0”，接下来获取本应用的包名，如果可以获取到，那么将 User Agent 设置为：App 的 packageName/versionCode，这个 User Agent 用来设置 http 请求头的 User-Agent 字段。</p>
<p>第 12 行，因为我们传入的 HttpStack 为 null，所以就根据当前系统的版本(即 API Level &gt;= 9)，采用基于 HttpURLConnection 的 HurlStack，如果小于 9，采用基于 HttpClient 的 HttpClientStack。我们知道在 Android 中默认有两种。关于为何在不同的版本使用不同的 HttpStack，参考  <a href="http://blog.csdn.net/guolin_blog/article/details/12452307" target="_blank" rel="noopener">Android 访问网络，使用 HttpURLConnection 还是 HttpClient？</a>，关于 HttpStack 的详细讲解，请参考<a href>Volley 源码解析-网络</a></p>
<p>第 22 行，创建了一个 NetWork 对象，其实 NetWork 是一个接口，其实现类只有 BasicNetwork；简单介绍一下 NetWork 接口，其内部只有一个方法 performRequest，而实现类 BasicNetwork 其内部调用 HttpStack 处理请求来实现 NetWork 中的方法，并将结果转换为可被 ResponseDelivery 处理的 NetworkResponse。</p>
<p>第 23 行，创建了一个 Cache 对象，其实 Cache 也是一个接口，其实现类只有 DiskBasedCache；简单介绍一下 Cache，Volley 默认使用的是基于 sdcard 的 DiskBasedCache。Cache 接口中包含了对于缓存文件中初始化，添加缓存，超找缓存，删除缓存等常用操作。并不执行网络请求。</p>
<p>第 25 行，终于看到了 RequestQueue 的创建了，其构造方法将上面创建的 Cache 和 Network 的实例传入，<strong>注意这里传入的是父类引用（接口），也就是说执行网络请求或者缓存读取的业务逻辑都是以接口的形式来提供的，所以后面的编码都是基于接口的，而不针对具体实现类，从而达到了高扩展性。</strong></p>
<p>在 Volley 中的静态方法 newRequestQueue 中，创建 RequestQueue 的方法是使用了<code>new RequestQueue(cache, network)</code>，那么进入源码看一下其是如何创建的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">RequestQueue</span><span class="params">(Cache cache, Network network)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>(cache, network, DEFAULT_NETWORK_THREAD_POOL_SIZE);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">RequestQueue</span><span class="params">(Cache cache, Network network, <span class="keyword">int</span> threadPoolSize)</span> </span>&#123;</span><br><span class="line">  	<span class="keyword">this</span>(cache, network, threadPoolSize,<span class="keyword">new</span> ExecutorDelivery(<span class="keyword">new</span> Handler(Looper.getMainLooper())));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">RequestQueue</span><span class="params">(Cache cache, Network network, <span class="keyword">int</span> threadPoolSize,ResponseDelivery delivery)</span> </span>&#123;</span><br><span class="line">	mCache = cache;</span><br><span class="line">	mNetwork = network;</span><br><span class="line">	mDispatchers = <span class="keyword">new</span> NetworkDispatcher[threadPoolSize];</span><br><span class="line">	mDelivery = delivery;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>源码中创建队列是使用的 RequestQueue 最简单的构造方法，通过一些列不断的调用，最终调用了四个参数的方法，这里先关注一下<strong>ResponseDelivery</strong>这个类，该类的作用是将网络请求的结果或者从缓存读取数据的结果分发到主线程。具体实现在后面分析。</p>
<p>不要被 RequestQueue 的类名所迷惑，RequestQueue 本身并不是一个队列，而是其内部包含了若干队列，这些队列分别有不同的作用，具体内部队列在下一节介绍；</p>
<p>第 26 行，执行了 queue 的 start 方法，这里轻描淡写的一句 start 确包含了整个 Volley 框架的所有业务流程。先简单说一下 start 内部的流程，RequestQueue 中其实包含了两种线程，一种用来执行网络请求，一种用来从本地文件中读取缓存，这两种线程不断的从 RequestQueue 中取出 request，然后执行该 request。这个 start 方法内部其实就是将这两类线程运行起来。</p>
<p>第 28 行，返回最终创建的 RequestQueue。</p>
<h1 id="向-RequestQueue-中添加请求"><a href="#向-RequestQueue-中添加请求" class="headerlink" title="向 RequestQueue 中添加请求"></a>向 RequestQueue 中添加请求</h1><p>虽然我们创建了 RequestQueue 队列之后，其内部的线程就已经开启了，这里先不讨论细节，在接下来开启线程的部分讨论。因为创建了队列后，队列中并没有 request，所以不会执行任何操作。这里先看一下我们使用 Volley 的第三步，向队列中添加请求，调用的是 RequestQueue 中的 add 方法：</p>
<p>在介绍 add 方法之前，这里还有两个重要的变量需要介绍一下，分别是：mCacheQueue 和 mNetworkQueue；这是两个优先级队列，是 RequestQueue 的成员变量，其直接在 RequestQueue 创建的时候进行初始化：</p>
<p>这里设计为优先级队列，主要是考虑到我们的 request 是有优先级的，有的请求可能想立即被执行，那么就将其优先级调大一些，从而获得优先执行权。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/** The cache triage queue. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> PriorityBlockingQueue&lt;Request&gt; mCacheQueue =</span><br><span class="line">    <span class="keyword">new</span> PriorityBlockingQueue&lt;Request&gt;();</span><br><span class="line"></span><br><span class="line"><span class="comment">/** The queue of requests that are actually going out to the network. */</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> PriorityBlockingQueue&lt;Request&gt; mNetworkQueue =</span><br><span class="line">    <span class="keyword">new</span> PriorityBlockingQueue&lt;Request&gt;();</span><br></pre></td></tr></table></figure>

<p>同时还有两个成员变量，mWaitingRequests 和 mCurrentRequests；</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map&lt;String, Queue&lt;Request&gt;&gt; mWaitingRequests = <span class="keyword">new</span> HashMap&lt;String, Queue&lt;Request&gt;&gt;();</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Set&lt;Request&gt; mCurrentRequests = <span class="keyword">new</span> HashSet&lt;Request&gt;();</span><br></pre></td></tr></table></figure>

<p>上述四个变量的作用：</p>
<ul>
<li><strong>mCacheQueue</strong>：</li>
<li><strong>mNetworkQueue</strong>：保存需要真正执行网络请求的 request</li>
<li><strong>mWaitingRequests</strong>：</li>
<li><strong>mCurrentRequests</strong>：HashSet，用来保存添加到 RequestQueue 中的请求，只要该请求还没有响应，就一直在 mCurrentRequests 中保存。</li>
</ul>
<p>接下来我们进入 add 方法中看一下其具体的流程：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Request <span class="title">add</span><span class="params">(Request request)</span> </span>&#123;</span><br><span class="line">	<span class="comment">// Tag the request as belonging to this queue and add it to the set of current requests.</span></span><br><span class="line">	request.setRequestQueue(<span class="keyword">this</span>);</span><br><span class="line">	<span class="keyword">synchronized</span> (mCurrentRequests) &#123;</span><br><span class="line">		mCurrentRequests.add(request);</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Process requests in the order they are added.</span></span><br><span class="line">	request.setSequence(getSequenceNumber());</span><br><span class="line">	request.addMarker(<span class="string">"add-to-queue"</span>);</span><br><span class="line"></span><br><span class="line">	<span class="comment">// If the request is uncacheable, skip the cache queue and go straight to the network.</span></span><br><span class="line">	<span class="keyword">if</span> (!request.shouldCache()) &#123;</span><br><span class="line">		mNetworkQueue.add(request);</span><br><span class="line">		<span class="keyword">return</span> request;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Insert request into stage if there's already a request with the same cache key in flight.</span></span><br><span class="line">	<span class="keyword">synchronized</span> (mWaitingRequests) &#123;</span><br><span class="line">		String cacheKey = request.getCacheKey();</span><br><span class="line">		<span class="keyword">if</span> (mWaitingRequests.containsKey(cacheKey)) &#123;</span><br><span class="line">			<span class="comment">// There is already a request in flight. Queue up.</span></span><br><span class="line">			Queue&lt;Request&gt; stagedRequests = mWaitingRequests.get(cacheKey);</span><br><span class="line">			<span class="keyword">if</span> (stagedRequests == <span class="keyword">null</span>) &#123;</span><br><span class="line">				stagedRequests = <span class="keyword">new</span> LinkedList&lt;Request&gt;();</span><br><span class="line">			&#125;</span><br><span class="line">			stagedRequests.add(request);</span><br><span class="line">			mWaitingRequests.put(cacheKey, stagedRequests);</span><br><span class="line">			<span class="keyword">if</span> (VolleyLog.DEBUG) &#123;</span><br><span class="line">				VolleyLog.v(<span class="string">"Request for cacheKey=%s is in flight, putting on hold."</span>, cacheKey);</span><br><span class="line">			&#125;</span><br><span class="line">		&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">			<span class="comment">// Insert 'null' queue for this cacheKey, indicating there is now a request in</span></span><br><span class="line">			<span class="comment">// flight.</span></span><br><span class="line">			mWaitingRequests.put(cacheKey, <span class="keyword">null</span>);</span><br><span class="line">			mCacheQueue.add(request);</span><br><span class="line">		&#125;</span><br><span class="line">		<span class="keyword">return</span> request;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>第 2 行，该 request 与当前 RequestQueue 进行关联绑定，标识该 request 是被添加到哪个 RequestQueue 中的。</p>
<p>第 5 行，将 request 添加到 mCurrentRequests 队列中。只要该请求还没有响应，就一直在 mCurrentRequests 中保存。</p>
<p>第 9、10 行，对添加进来的 request 设置一个序列号，作为区分不同的请求。addMarker 方法也会在后面的源码中经常遇到，其实就是为当前的 request 添加一个状态的描述，此时添加的描述为”add-to-queue”，表明该请求才刚被添加进来，还未执行。</p>
<p>第 13 行，判断该 request 是否是可以缓存的，如果我们不做特殊设置，所有的 request 默认都是可以缓存的，这样做是为了减少流量的使用。当然如果你的应用对实时性要求很高，每次请求都要获取服务器最新的数据，那么可以将调用<code>request.setShouldCache(false);</code>取消缓存，这样的话，该 request 就会被添加到 mNetworkQueue 队列中，那么 RequestQueue 的 add 方法就结束了</p>
<p>第 19 行，能走到这一步，说明 request 是可以被缓存的，那么首先获取该 request 的 getCacheKey 方法，该方法简单的返回了该请求的 url，接下来就判断 mWaitingRequests(HashMap)中是否存在以该 request 的 url 作为 key 的值，这里分两种情况来说，如果当前 request 是一个全新的请求，那么 mWaitingRequests 中肯定不存在该 key 的，所以走 else 分支，如果存在，那么说明我们之前执行过相同的请求，走 if 分支；接下来分别看一下这两个分支</p>
<p>第 31 行，这是一个全新的请求，那么在 mWaitingRequests 中，将该 request 的 url 作为 key 添加进去，但是其 value 设置 null，并将该 request 添加到 mCacheQueue 中。</p>
<p>第 21 行，这不是一个新的请求，之前执行过。那么拿到该 key 对应的 value，其 value 值是一个<code>LinkedList&lt;Request&gt;</code>,然后将该请求添加到这个 value 中。这里要注意的是：如果是第二次执行这个请求，会走到该 if 分支，但是从 mWaitingRequests 中获取 value 时是空的，因为第一次执行该请求时，走的是 else 分支，当时只是把 key 添加进来了，value 是空的，所以如果是第二次执行的话，获取的是一个空的 LinkedList，此时需要重新创建一个 LinkedList，并将自己添加进去，那么及诶按来如果是第三次，第四次等添加该请求时，就可以直接拿到 LinkedList，然后直接添加进去即可。</p>
<p><strong>总结</strong></p>
<p>request 被添加到 RequestQueue 中时，所经历的过程：</p>
<p><img src="http://oi8e3sh89.bkt.clouddn.com/image/%E6%A1%86%E6%9E%B6/Volley%E5%86%85%E9%83%A8%E6%B5%81%E7%A8%8B.png" alt></p>
<h1 id="开启请求队列中的线程"><a href="#开启请求队列中的线程" class="headerlink" title="开启请求队列中的线程"></a>开启请求队列中的线程</h1><p>前面我们讲解了 RequestQueue 的创建时，调用了其 start 方法，这个方法虽然只有一句，但是其内部却是整个框架的核心，一起来看一下 start 方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    stop();  <span class="comment">// Make sure any currently running dispatchers are stopped.</span></span><br><span class="line">    <span class="comment">// Create the cache dispatcher and start it.</span></span><br><span class="line">    mCacheDispatcher = <span class="keyword">new</span> CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);</span><br><span class="line">    mCacheDispatcher.start();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Create network dispatchers (and corresponding threads) up to the pool size.</span></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; mDispatchers.length; i++) &#123;</span><br><span class="line">        NetworkDispatcher networkDispatcher = <span class="keyword">new</span> NetworkDispatcher(mNetworkQueue, mNetwork,mCache, mDelivery);</span><br><span class="line">        mDispatchers[i] = networkDispatcher;</span><br><span class="line">        networkDispatcher.start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这里涉及到两个重要的线程，我们在文章刚一开始根据 Volley 的使用方法，猜想其内部实现时也说到了，Volley 内部一定有一个线程不断的从 RequestQueue 中出请求，然后执行该请求。同时提供了缓存机制，就需要另一个线程从本地缓存中去取缓存。这分别对应了两个线程调度器：<strong>CacheDispatcher</strong>和<strong>NetworkDispatcher</strong>。</p>
<p>这里先说一下这两个调度器的作用：</p>
<ul>
<li><strong>CacheDispatcher：</strong>（extends Thread），用于调度处理走缓存的请求。启动后会不断从缓存请求队列中取请求处理，队列为空则等待，请求处理结束则将结果传递给<code>ResponseDelivery</code>去执行后续处理。当结果未缓存过、缓存失效或缓存需要刷新的情况下，该请求都需要重新进入<code>NetworkDispatcher</code>去调度处理。</li>
<li><strong>NetworkDispatcher</strong>：（extends Thread），用于调度处理走网络的请求。启动后会不断从网络请求队列中取请求处理，队列为空则等待，请求处理结束则将结果传递给<code>ResponseDelivery</code>去执行后续处理，并判断结果是否要进行缓存。</li>
</ul>
<p>那么在 start 方法中：</p>
<p>第 3 行，首先创建了一个 mCacheDispatcher 线程的实例，然后让该线程运行起来。</p>
<p>第 8 行，创建了 4 个 NetworkDispatcher 的线程实例，然后让着 4 个线程运行起来。这里为什么是 4 个，这是因为在创建 RequestQueue 的时候使用的是最简单的构造方法，只传入了 Cache 和 Network 两个参数。而 threadPoolSize 的参数使用了默认的 4，如果你想改变，可以使用 RequestQueue 三个参数的构造方法，第三个参数来指定 NetworkDispatcher 线程的数量。</p>
<p>目前总共 5 个线程就这样运行起来了，后面会用两个章节来分别讲解 Volley 中的网络请求（Network）和缓存（Cache）相关的部分，敬请期待。</p>




    </div>










    <footer class="post-footer">








        <div class="post-eof"></div>

    </footer>
  </div>



  </article>











  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">



  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zachaxy.github.io/2017/05/20/Volley框架详解-基本使用/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zachaxy">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zachaxy">
    </span>


      <header class="post-header">



          <h2 class="post-title" itemprop="name headline">

                <a class="post-title-link" href="/2017/05/20/Volley框架详解-基本使用/" itemprop="url">Volley框架详解-基本使用</a></h2>


        <div class="post-meta">
          <span class="post-time">

              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>

                <span class="post-meta-item-text">发表于</span>

              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-05-20T21:52:01+08:00">
                2017-05-20
              </time>





          </span>













            <div class="post-wordcount">


                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>

                  <span class="post-meta-item-text">字数统计&#58;</span>

                <span title="字数统计">
                  2,634
                </span>





            </div>




        </div>
      </header>





    <div class="post-body" itemprop="articleBody">







            <blockquote>
<p>最近在往博客上整理一些之前看过的框架，翻到了已经推出很久的网络请求框架—— Volley。这是在 13 年由谷歌推出的一款轻量级的 Android 异步网络请求框架和图片加载框架。虽然现在市面上已经出现了比 Volley 更好的网络请求框架——Okhttp，和图片请求框架——Glide，但是个人认为 Volley 的源码比其它框架的源码更加优美，所以 Volley 的源码非常值得初学者拿来分析练手。</p>
</blockquote>
<h1 id="Volley-简介"><a href="#Volley-简介" class="headerlink" title="Volley 简介"></a>Volley 简介</h1><p>Volley 是 Google 推出的 Android 异步网络请求框架和图片加载框架。在 Google I/O 2013 大会上发布。</p>
<p>Volley 的特点：特别适合<strong>数据量小，通信频繁</strong>的网络操作。</p>
<h1 id="Volley-的主要特点"><a href="#Volley-的主要特点" class="headerlink" title="Volley 的主要特点"></a>Volley 的主要特点</h1><ol>
<li>扩展性强。Volley 中大多是<strong>基于接口</strong>的设计，可配置性强。</li>
<li>一定程度符合 Http 规范，包括返回 ResponseCode(2xx、3xx、4xx、5xx）的处理，请求头的处理，缓存机制的支持等。并支持重试及优先级定义。</li>
<li>默认 Android2.3 及以上基于 HttpURLConnection，2.3 以下基于 HttpClient 实现。所以兼容性强。</li>
</ol>
<h1 id="Volley-的基本用法"><a href="#Volley-的基本用法" class="headerlink" title="Volley 的基本用法"></a>Volley 的基本用法</h1><p>在使用 Volley 之前，不要忘记在你的 manifest 文件中添加网络权限。</p>
<h2 id="（一）创建请求队列"><a href="#（一）创建请求队列" class="headerlink" title="（一）创建请求队列"></a>（一）创建请求队列</h2><p>首先需要创建一个 RequestQueue 对象，用来保存所有的网路请求 request，其获取方法：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">RequestQueue mQueue = Volley.newRequestQueue(context);</span><br></pre></td></tr></table></figure>

<p>这里要注意的是：RequestQueue 内部的设计适合高并发，因此我们不必为每一次 HTTP 请求都创建一个 RequestQueue 对象，这是非常浪费资源的，基本上一个 RequestQueue 对象就足够了。所以我们将 RequestQueue 的获取放在自定义的 Application 中：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">MyApp</span> <span class="keyword">extends</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> RequestQueue queues;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onCreate</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>.onCreate();</span><br><span class="line">        queues = Volley.newRequestQueue(getApplicationContext());</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> RequestQueue <span class="title">getHttpQueues</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> queues;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="（二）创建请求"><a href="#（二）创建请求" class="headerlink" title="（二）创建请求"></a>（二）创建请求</h2><p>Volley 自带的请求有：</p>
<ol>
<li>StringRequest</li>
<li>JsonRequest</li>
<li>ImageRequest</li>
</ol>
<p>一般我们请求的数据都是当做字符串来返回的，如果返回的结果使我们自定义的简单的结果，那么直接解析返回的字符串即可，但如果返回的结果是 Json 或者 Xml，那么就需要我们手动将返回的字符串转换为对应的 Json 或 Xml，好在 Volley 已经为我们实现了 Json，后面我们会学习如何自定义 Xml 的请求。</p>
<p>常见的请求又分为两种，一种是 GET 请求，一种是 POST 请求，这里以 StringRequest 为例，分别创建 GET 请求和 POST 请求：</p>
<h3 id="GET-请求"><a href="#GET-请求" class="headerlink" title="GET 请求"></a>GET 请求</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">String url = <span class="string">"http://op.juhe.cn/onebox/phone/query?tel=18220197835&amp;key=xxxxxxxxxxxxxxxxxxx"</span>;</span><br><span class="line"><span class="comment">//使用 get 方法;传入 URL;请求成功的监听回调;请求失败的监听回调;</span></span><br><span class="line">StringRequest request = <span class="keyword">new</span> StringRequest(Request.Method.GET, url, <span class="keyword">new</span> Response.Listener&lt;String&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResponse</span><span class="params">(String s)</span> </span>&#123;</span><br><span class="line">        <span class="comment">//System.out.println(s);</span></span><br><span class="line">        Log.e(<span class="string">"volley_GetByString"</span>, <span class="string">"onResponse: "</span> + s);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="keyword">new</span> Response.ErrorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onErrorResponse</span><span class="params">(VolleyError volleyError)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line">request.setTag(<span class="string">"abc"</span>);</span><br><span class="line">MyApp.getHttpQueues().add(request);</span><br></pre></td></tr></table></figure>

<p>创建 GET 请求，只需要在创建的 StringRequest 的构造方法的第一个参数中传入常量 Request.Method.GET 即可；第二个参数是所请求的 URL，第三个参数是请求成功的回调，这是一个接口，需要我们实现 onResponse 方法，该方法中传入的参数就是服务器给我们回调的结果；第四个参数是请求失败的回调，也是一个接口，需要我们实现 onErrorResponse，我们可以根据自己的业务需求，分别在请求成功或者失败的回调中处理不同的逻辑；</p>
<p>至此，一个请求就已经创建好了，如何发送请求呢？很简单，只需要将该请求添加到我们在上一步中创建的 RequestQueue 中即可。这里我们为 request 打上了一个标签，这个标签有什么用？在 Volley 与 Activity 生命周期的联动中会用到，别急。</p>
<h3 id="POST-请求"><a href="#POST-请求" class="headerlink" title="POST 请求"></a>POST 请求</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">String url = <span class="string">"http://op.juhe.cn/onebox/phone/query?"</span>;</span><br><span class="line">StringRequest request = <span class="keyword">new</span> StringRequest(Request.Method.POST, url, <span class="keyword">new</span> Response.Listener&lt;String&gt;() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onResponse</span><span class="params">(String s)</span> </span>&#123;</span><br><span class="line">        PhoneMsg msg;</span><br><span class="line">        Gson gson = <span class="keyword">new</span> Gson();</span><br><span class="line">        msg = gson.fromJson(s, PhoneMsg.class);</span><br><span class="line">        Log.e(<span class="string">"###"</span>, <span class="string">"volley_PostByString: "</span> + msg.reason + <span class="string">"&lt;&gt;"</span> + msg.result.city + <span class="string">"&lt;&gt;"</span> + msg.error_code);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;, <span class="keyword">new</span> Response.ErrorListener() &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onErrorResponse</span><span class="params">(VolleyError volleyError)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;) &#123;</span><br><span class="line">    <span class="comment">//StringRequest 的父类中的方法,这里我们是创建了一个匿名 StringRequest,重写了其 getParams 方法;</span></span><br><span class="line">    <span class="comment">//当使用 post 方法时,会自动回调该方法,得到一个 map,并将其中的 key-value 以 key1=value1&amp;key2=value2&amp; 的形式拼接起来</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> Map&lt;String, String&gt; <span class="title">getParams</span><span class="params">()</span> <span class="keyword">throws</span> AuthFailureError </span>&#123;</span><br><span class="line">        Map&lt;String, String&gt; hashMap = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        hashMap.put(<span class="string">"tel"</span>, <span class="string">"18220197835"</span>);</span><br><span class="line">        hashMap.put(<span class="string">"key"</span>, <span class="string">"xxxxxxxxxxxxxxxxxx"</span>);</span><br><span class="line">        <span class="keyword">return</span> hashMap;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">request.setTag(<span class="string">"abc"</span>);</span><br><span class="line">MyApp.getHttpQueues().add(request);</span><br></pre></td></tr></table></figure>

<p>POST 请求和 GET 请求类似，只不过在创建的 StringRequest 第一个参数改为常量 Request.Method.POST 即可，第二个参数 URL 和 get 不同了，因为我们这里是用 POST 方法发送请求，那么请求的 key-value 都不会出现在 url 中，而是重写了 StringRequest 的 getParams 方法，创建了一个 map，将请求的 key-value 保存到 map 中，然后将该 map 返回即可，在 post 请求发送时会自动回调 getParams 方法,并得到的 map 中的 key-value 以 key1=value1&amp;key2=value2&amp; 的形式拼接起来，添加到请求体中。第三个参数和第四个参数与 GET 请求相同。</p>
<h1 id="Volley-与-Activity-生命周期的联动"><a href="#Volley-与-Activity-生命周期的联动" class="headerlink" title="Volley 与 Activity 生命周期的联动"></a>Volley 与 Activity 生命周期的联动</h1><p>在创建了请求之后，将请求添加到 RequestQueue 中，</p>
<p>考虑这样一种场景，如果我们在当前的 Activity 中创建了大量的 request，但是这时突然用户有点击了 back 键或者跳转到了另一个 Activity，那么还未执行的 request 此时就没有必要去执行了，如何取消还未执行的请求呢？注意我们在每个 request 中都绑定了一个 tag，<code>request.setTag(&quot;abc&quot;);</code>，如果想取消这些 request，只需要在 Activity 的 onStop 回调中调用 cancelAll 方法，取消特定的 tag，那么之前发出的请求将不会被执行。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">onStop</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">super</span>.onStop();</span><br><span class="line">    MyApp.getHttpQueues().cancelAll(<span class="string">"abc"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="自定义请求"><a href="#自定义请求" class="headerlink" title="自定义请求"></a>自定义请求</h1><p>前面也介绍了 Volley 的一个特点就是高扩展性，官方也为我们提供了 StringRequest，JsonRequest 等现成的 request，但是如果我们的业务用的是我们一种公司自定义的形式，那么我们就有必要自定义请求来解析返回的结果。</p>
<p>首先要明确的是：最原始的请求返回的结果都是字节流，我们正是通过对字节流的转换，才得到了我们想要的请求，这就是自定义 request 的本质。</p>
<p>JsonRequest 的数据解析是利用 Android 本身自带的 JSONObject 和 JSONArray 来实现的，配合使用 JSONObject 和 JSONArray 就可以解析出任意格式的 JSON 数据。但是使用 JSONObject 还是太麻烦了，还有很多方法可以让 JSON 数据解析变得更加简单，比如说 GSON。那么接下来我们自定义一个 GsonRequest。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">GsonRequest</span>&lt;<span class="title">T</span>&gt; <span class="keyword">extends</span> <span class="title">Request</span>&lt;<span class="title">T</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> Response.Listener&lt;T&gt; mListener;</span><br><span class="line">    <span class="keyword">private</span> Gson mGson;</span><br><span class="line">    <span class="keyword">private</span> Class&lt;T&gt; mClass;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">GsonRequest</span><span class="params">(<span class="keyword">int</span> method, String url, Class&lt;T&gt; clazz, Response.Listener&lt;T&gt; listener,</span></span></span><br><span class="line"><span class="function"><span class="params">                       Response.ErrorListener elistener)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(method, url, elistener);</span><br><span class="line">        mGson = <span class="keyword">new</span> Gson();</span><br><span class="line">        mClass = clazz;</span><br><span class="line">        mListener = listener;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">GsonRequest</span><span class="params">(String url, Class&lt;T&gt; clazz, Response.Listener&lt;T&gt; listener,</span></span></span><br><span class="line"><span class="function"><span class="params">                       Response.ErrorListener errorListener)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(Method.GET, url, clazz, listener, errorListener);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/*</span></span><br><span class="line"><span class="comment">    这是响应最原始的面貌,得到的就是一个 NetWorkResponse,至于应用层想得到什么类型的响应,需要在这里进行转换;</span></span><br><span class="line"><span class="comment">    这个过程和 StringRequest 的构造很像,具体可以参考 StringRequest;</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> Response <span class="title">parseNetworkResponse</span><span class="params">(NetworkResponse response)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="comment">//这一句是固定格式,先将 response 的 data 域拿出来,其是字节,要转换成字符串,同时要获取该字节的编码;</span></span><br><span class="line">            String jsonString = <span class="keyword">new</span> String(response.data, HttpHeaderParser.parseCharset(response.headers));</span><br><span class="line">            <span class="keyword">return</span> Response.success(mGson.fromJson(jsonString, mClass), HttpHeaderParser.parseCacheHeaders(response));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (UnsupportedEncodingException e) &#123;</span><br><span class="line">            <span class="keyword">return</span> Response.error(<span class="keyword">new</span> ParseError(e));</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">   </span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">deliverResponse</span><span class="params">(T response)</span> </span>&#123;</span><br><span class="line">        mListener.onResponse(response);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>自定义 request 需要继承 Request 类，而 Request 是一个抽象类，需要我们实现其抽象方法，包含两个：parseNetworkResponse 和 deliverResponse。</p>
<p>第一个要实现的方法 parseNetworkResponse，该方法是得到服务器响应时回调的结果，其参数是执行网络请求得到的响应 NetworkResponse，我们可以通过 NetworkResponse.data 拿到包含在响应中的字节数组，然后通过 HttpHeaderParser.parseCacheHeaders(response)获取响应中的编码格式，将该字节数组转换为对应的字符串。如果字符串解析成功，接下来我们就可以调用 Response 中的 success 方法，将结果传入进去，如果解码失败，那么调用 Response 的 error 方法。</p>
<p>第二个要实现的方法是 deliverResponse，这个方法也必须实现,该方法的参数是从上一步 parseNetworkResponse 中 Response.success()第一个参数得到的响应,我们要把这个响应分发下去;注意这一步说明我们已经得到成功的响应了,而之前 errorListener 的响应则是在网络出错或者服务器出错的情况下分发的 是由 Volley 顶层实现的。</p>
<p>我们看一下Volley自带的StringRequest是如何实现的：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">StringRequest</span> <span class="keyword">extends</span> <span class="title">Request</span>&lt;<span class="title">String</span>&gt; </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> Listener&lt;String&gt; mListener;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates a new request with the given method.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> method the request &#123;<span class="doctag">@link</span> Method&#125; to use</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url URL to fetch the string at</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> listener Listener to receive the String response</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> errorListener Error listener, or null to ignore errors</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">StringRequest</span><span class="params">(<span class="keyword">int</span> method, String url, Listener&lt;String&gt; listener,</span></span></span><br><span class="line"><span class="function"><span class="params">            ErrorListener errorListener)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">super</span>(method, url, errorListener);</span><br><span class="line">        mListener = listener;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Creates a new GET request.</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> url URL to fetch the string at</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> listener Listener to receive the String response</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> errorListener Error listener, or null to ignore errors</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">StringRequest</span><span class="params">(String url, Listener&lt;String&gt; listener, ErrorListener errorListener)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>(Method.GET, url, listener, errorListener);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">deliverResponse</span><span class="params">(String response)</span> </span>&#123;</span><br><span class="line">        mListener.onResponse(response);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> Response&lt;String&gt; <span class="title">parseNetworkResponse</span><span class="params">(NetworkResponse response)</span> </span>&#123;</span><br><span class="line">        String parsed;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            parsed = <span class="keyword">new</span> String(response.data, HttpHeaderParser.parseCharset(response.headers));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (UnsupportedEncodingException e) &#123;</span><br><span class="line">            parsed = <span class="keyword">new</span> String(response.data);</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>是不是相同的套路，同样是拿到了服务器的字节数组之后，直接转换为了字符串，然后就返回了，这个我们自定义的GsonRequest和StringRequest的套路是一致的。</p>
<p>至此，关于Volley的基本使用就讲完了，具体的使用请参考<a href="https://github.com/zachaxy/android-project-demo/blob/master/TestVolley/app/src/main/java/com/zx/testvolley/MainActivity.java" target="_blank" rel="noopener">这里</a>，下一篇，会进入Volley的源码，详细解剖其内部的基本实现。</p>




    </div>










    <footer class="post-footer">








        <div class="post-eof"></div>

    </footer>
  </div>



  </article>











  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">



  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://zachaxy.github.io/2017/05/15/手写JVM系列-10-解释器/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="zachaxy">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="zachaxy">
    </span>


      <header class="post-header">



          <h2 class="post-title" itemprop="name headline">

                <a class="post-title-link" href="/2017/05/15/手写JVM系列-10-解释器/" itemprop="url">手写JVM系列(10)-解释器</a></h2>


        <div class="post-meta">
          <span class="post-time">

              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>

                <span class="post-meta-item-text">发表于</span>

              <time title="创建于" itemprop="dateCreated datePublished" datetime="2017-05-15T11:44:03+08:00">
                2017-05-15
              </time>





          </span>













            <div class="post-wordcount">


                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>

                  <span class="post-meta-item-text">字数统计&#58;</span>

                <span title="字数统计">
                  1,714
                </span>





            </div>




        </div>
      </header>





    <div class="post-body" itemprop="articleBody">







            <blockquote>
<p>上一节介绍了关于 JVM 指令集的相关知识，那么接下来我们就根据上一节介绍的知识，编写一个简易的解释器，读取字节码中的操作码和操作数，实现 JVM 规范中描述的指令集对应功能，从而完成简单方法的调用过程，并在接下来的章节中不断完善该解释器，使其支持更多指令。本节所实现的代码均在<a href="https://github.com/zachaxy/JVM" target="_blank" rel="noopener">项目</a>的 instructions 包下。</p>
</blockquote>
<h1 id="操作码的实现"><a href="#操作码的实现" class="headerlink" title="操作码的实现"></a>操作码的实现</h1><p>在 Code 属性中的字节数组就是字节码。操作码规定长度为一个字节，所以这里先读取一个字节，查看其对应的是哪种操作码，然后根据该操作码的实际含义，再决定是不是后面还有操作数需要读取。这一节实现了约 150 条指令，这些指令根据其分类，在 instructions 包下的子包中，具体实现见<a href="https://github.com/zachaxy/JVM" target="_blank" rel="noopener">项目源码</a>。</p>
<h1 id="指令抽象接口"><a href="#指令抽象接口" class="headerlink" title="指令抽象接口"></a>指令抽象接口</h1><p>有了前面的基础知识，接下来我们就要从字节码中读取指令，并执行指令，完成解释器的功能了。首先定义指令接口，然后定义一个结构体用来辅助指令解码。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Instruction</span> </span>&#123;</span><br><span class="line">    <span class="comment">//从字节码中提取操作数</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">fetchOperands</span><span class="params">(BytecodeReader reader)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">//执行指令逻辑</span></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">execute</span><span class="params">(Zframe frame)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这是所有指令都要完成的两个动作，提取操作数（如果有的话），执行指令。</p>
<h1 id="指令抽象类"><a href="#指令抽象类" class="headerlink" title="指令抽象类"></a>指令抽象类</h1><p>有很多指令的操作数都是类似的。为了避免重复代码，按照操作码类型定义一些抽象类，这些抽象类实现了 Instruction 接口，并实现 FetchOperands（）方法。再由具体的类去继承这些抽象类，然后<br>专注实现 Execute（）方法即可。定义的抽象类有以下几种。</p>
<h2 id="NoOperandsInstruction"><a href="#NoOperandsInstruction" class="headerlink" title="NoOperandsInstruction"></a>NoOperandsInstruction</h2><p>NoOperandsInstruction 表示没有操作数的指令，所以没有定义任何字段。FetchOperands（）方法自然也是空空如也，什么也不用读。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">NoOperandsInstruction</span> <span class="keyword">implements</span> <span class="title">Instruction</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fetchOperands</span><span class="params">(BytecodeReader reader)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="BranchInstruction"><a href="#BranchInstruction" class="headerlink" title="BranchInstruction"></a>BranchInstruction</h2><p>BranchInstruction 表示跳转指令，Offset 字段存放跳转偏移量。FetchOperands（）方法从字节码中读取一个 uint16 整数，转成 int 后赋给 Offset 字段。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">BranchInstruction</span> <span class="keyword">implements</span> <span class="title">Instruction</span> </span>&#123;</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> offset;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fetchOperands</span><span class="params">(BytecodeReader reader)</span> </span>&#123;</span><br><span class="line">        offset = reader.readInt16();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Index8Instruction"><a href="#Index8Instruction" class="headerlink" title="Index8Instruction"></a>Index8Instruction</h2><p>存储和加载类指令需要根据索引存取局部变量表，索引由单字节操作数给出。把这类指令抽象成 Index8Instruction 结构体，用 Index 字段表示局部变量表索引。FetchOperands（）方法从字节码中读取一个 int8 整数，转成 uint 后赋给 Index 字段。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Index8Instruction</span> <span class="keyword">implements</span> <span class="title">Instruction</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Index8Instruction</span><span class="params">()</span></span>&#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span> index;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fetchOperands</span><span class="params">(BytecodeReader reader)</span> </span>&#123;</span><br><span class="line">        index = reader.readUint8();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="Index16Instruction"><a href="#Index16Instruction" class="headerlink" title="Index16Instruction"></a>Index16Instruction</h2><p>有一些指令需要访问运行时常量池，常量池索引由两字节操作数给出。把这类指令抽象成 Index16Instruction 结构体，用 Index 字段表示常量池索引。FetchOperands（）方法从字节码中读取一个 uint16 整数，转成 uint 后赋给 Index 字段。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Index16Instruction</span> <span class="keyword">implements</span> <span class="title">Instruction</span> </span>&#123;</span><br><span class="line">    <span class="keyword">int</span> index;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">fetchOperands</span><span class="params">(BytecodeReader reader)</span> </span>&#123;</span><br><span class="line">        index = reader.readUint16();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="读取字节码辅助类"><a href="#读取字节码辅助类" class="headerlink" title="读取字节码辅助类"></a>读取字节码辅助类</h1><p>code 字段存放字节码，pc 字段记录读取到了哪个字节。所有的指令都要从字节码中读取数据，并根据不同的指令类型读取 int、long、float、double 等类型的数据，所以定义一个读取字节码的辅助类 BytecodeReader，让其实现一系列的 readXXX（）方法。至于具体调用哪个 read 方法，则根据操作码中对应的操作类型来调用。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BytecodeReader</span> </span>&#123;</span><br><span class="line">    <span class="keyword">byte</span>[] code;  <span class="comment">//Java 语言中 byte 的范围四 -128~127,和 Go 语言中的 byte:0~255 不同,所以在取数据的时候需要注意;</span></span><br><span class="line">    <span class="keyword">int</span> pc;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">reset</span><span class="params">(<span class="keyword">byte</span>[] code, <span class="keyword">int</span> pc)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.code = code;</span><br><span class="line">        <span class="keyword">this</span>.pc = pc;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getPc</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> pc;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">byte</span> <span class="title">readInt8</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">byte</span> res = code[pc];</span><br><span class="line">        pc++;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">readUint8</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> res = code[pc];</span><br><span class="line">        res = (res + <span class="number">256</span>) % <span class="number">256</span>;</span><br><span class="line">        pc++;</span><br><span class="line">        <span class="keyword">return</span> res;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">readInt16</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> (<span class="keyword">short</span>) readUint16();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span>  <span class="keyword">int</span> <span class="title">readUint16</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">int</span> a1 = readUint8();</span><br><span class="line">        <span class="keyword">int</span> a2 = readUint8();</span><br><span class="line">        <span class="keyword">return</span> (a1 &lt;&lt; <span class="number">8</span> | a2);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">readInt32</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">byte</span>[] data = <span class="keyword">new</span> <span class="keyword">byte</span>[<span class="number">4</span>];</span><br><span class="line">        data[<span class="number">0</span>] = readInt8();</span><br><span class="line">        data[<span class="number">1</span>] = readInt8();</span><br><span class="line">        data[<span class="number">2</span>] = readInt8();</span><br><span class="line">        data[<span class="number">3</span>] = readInt8();</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> ByteUtils.byteToInt32(data);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">int</span>[] readInt32s(<span class="keyword">int</span> n) &#123;</span><br><span class="line">        <span class="keyword">int</span>[] data = <span class="keyword">new</span> <span class="keyword">int</span>[n];</span><br><span class="line">        <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">            data[i] = readInt32();</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> data;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">//4k 对齐,没有对齐的会有填充数据,这些数据要忽略掉;</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">skipPadding</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">while</span> (pc % <span class="number">4</span> != <span class="number">0</span>) &#123;</span><br><span class="line">            readInt8();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="指令创建工厂"><a href="#指令创建工厂" class="headerlink" title="指令创建工厂"></a>指令创建工厂</h1><p>有了前面的各个指令的具体实现，接下来统一对外提供一个创建指令的接口，根据不同的操作码创建具体的指令，整体结构就是一个 switch-case 语句，其枚举了这一节已经实现的 150 多种指令，代码太长就不列举了，具体实现在 instructions 包下的 InstructionFactory 类中。</p>
<h1 id="解释器"><a href="#解释器" class="headerlink" title="解释器"></a>解释器</h1><p>Java 虚拟机解释器的大致逻辑，其伪码如下所示：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">do</span> &#123;</span><br><span class="line">    atomically calculate pc and fetch opcode at pc;</span><br><span class="line">    <span class="keyword">if</span> (operands) fetch operands;</span><br><span class="line">    execute the action <span class="keyword">for</span> the opcode;</span><br><span class="line">&#125; <span class="keyword">while</span> (there is more to <span class="keyword">do</span>);</span><br></pre></td></tr></table></figure>

<p>解释一下上面的过程：</p>
<ol>
<li>计算 pc 值（默认 pc++，取下一条指令，否则就是跳转指令，对应 pc + offset ）；</li>
<li>根据 pc 寄存器的指示位置，从字节码中读取出操作码；</li>
<li>如果该操作码存在操作数，那么继续从字节码中读取操作数；</li>
<li>执行操作码所定义的操作；</li>
<li>如果字节码还未读取完，继续步骤 1；</li>
</ol>
<p>我们根据前面已经实现的编码，创建 Interpreter 类，实现指令的执行过程，代码如下：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">loop</span><span class="params">(Zthread thread, <span class="keyword">byte</span>[] byteCode)</span> </span>&#123;</span><br><span class="line">    Zframe frame = thread.popFrame();   <span class="comment">//得到栈顶的帧。</span></span><br><span class="line">    BytecodeReader reader = <span class="keyword">new</span> BytecodeReader();</span><br><span class="line"></span><br><span class="line">    <span class="comment">//这里循环的条件是 true,因为在解析指令的时候会遇到 return,而现在还没有实现 return,所以遇到 return 直接抛出异常,那么循环也就终止了</span></span><br><span class="line">    <span class="keyword">while</span> (<span class="keyword">true</span>) &#123;</span><br><span class="line">        <span class="keyword">int</span> pc = frame.getNextPC(); <span class="comment">//这第一次 frame 才刚初始化，获取的 pc 应该是默认值 0 吧。</span></span><br><span class="line">        thread.setPc(pc);</span><br><span class="line">        reader.reset(byteCode, pc); <span class="comment">//reset 方法，其实是在不断的设置 pc 的位置。</span></span><br><span class="line">        <span class="keyword">int</span> opCode = reader.readUint8();</span><br><span class="line">        <span class="comment">//解析指令,创建指令,然后根据不同的指令执行不同的操作</span></span><br><span class="line">        Instruction instruction = InstructionFactory.createInstruction(opCode);</span><br><span class="line">        instruction.fetchOperands(reader);</span><br><span class="line">        frame.setNextPC(reader.getPc());</span><br><span class="line"></span><br><span class="line">        System.out.printf(<span class="string">"pc: %2d, inst: %s \n"</span>, pc, instruction.getClass().getSimpleName());</span><br><span class="line">        instruction.execute(frame);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>其中 Interpreter#loop（）方法中，while 循环部分的代码就是 Java 虚拟机规范中 JVM 解释器的具体实现。</p>




    </div>










    <footer class="post-footer">








        <div class="post-eof"></div>

    </footer>
  </div>



  </article>



  </section>


  <nav class="pagination">
    <a class="extend prev" rel="prev" href="/page/7/"><i class="fa fa-angle-left"></i></a><a class="page-number" href="/">1</a><span class="space">&hellip;</span><a class="page-number" href="/page/7/">7</a><span class="page-number current">8</span><a class="page-number" href="/page/9/">9</a><span class="space">&hellip;</span><a class="page-number" href="/page/13/">13</a><a class="extend next" rel="next" href="/page/9/"><i class="fa fa-angle-right"></i></a>
  </nav>



          </div>





        </div>



  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">

      <div id="sidebar-dimmer"></div>

    <div class="sidebar-inner">





      <section class="site-overview-wrap sidebar-panel sidebar-panel-active">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">

              <img class="site-author-image" itemprop="image" src="/images/avatar.jpg" alt="zachaxy">

              <p class="site-author-name" itemprop="name">zachaxy</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">


              <div class="site-state-item site-state-posts">

                <a href="/archives">

                  <span class="site-state-item-count">64</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>







              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">12</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>


          </nav>



          <div class="links-of-author motion-element">


                <span class="links-of-author-item">
                  <a href="https://github.com/zachaxy" target="_blank" title="GitHub">

                      <i class="fa fa-fw fa-globe"></i>GitHub</a>
                </span>

                <span class="links-of-author-item">
                  <a href="https://weibo.com/3242765540" target="_blank" title="Weibo">

                      <i class="fa fa-fw fa-globe"></i>Weibo</a>
                </span>


          </div>









        </div>
      </section>





    </div>
  </aside>



      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">zachaxy</span>


    <span class="post-meta-divider">|</span>
    <span class="post-meta-item-icon">
      <i class="fa fa-area-chart"></i>
    </span>

      <span class="post-meta-item-text">站点文章字数总计&#58;</span>

    <span title="站点文章字数总计">147.9k</span>

</div>






  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a></div>













      </div>
    </footer>


      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>

          <span id="scrollpercent"><span>0</span>%</span>

      </div>




  </div>



<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>
























    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>




    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>




    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>




    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>




    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>




    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>






  <script type="text/javascript" src="/js/src/utils.js?v=5.1.3"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.3"></script>











  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.3"></script>







































  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>

















  <script type="text/javascript" src="/js/src/js.cookie.js?v=5.1.3"></script>
  <script type="text/javascript" src="/js/src/scroll-cookie.js?v=5.1.3"></script>





</body>
</html>
